fix: debug_traceTransaction default param#9788
Conversation
Signed-off-by: Sally MacFarlane <macfarla.github@gmail.com>
Signed-off-by: Sally MacFarlane <macfarla.github@gmail.com>
Signed-off-by: Sally MacFarlane <macfarla.github@gmail.com>
…ue, matching the documented API behavior Signed-off-by: Sally MacFarlane <macfarla.github@gmail.com>
Signed-off-by: Sally MacFarlane <macfarla.github@gmail.com>
Signed-off-by: Sally MacFarlane <macfarla.github@gmail.com>
Signed-off-by: Sally MacFarlane <macfarla.github@gmail.com>
|
@lu-pinto can you review this one - the default param behavior is currently inconsistent between RPCs - this came up from looking at a docs PR hyperledger/besu-docs#1909 - in one spot we say the trace flags are all false by default but looking at the code that's not the case, and actually you currently get different behavior between passing no options and empty {} options |
...va/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/TransactionTraceParams.java
Outdated
Show resolved
Hide resolved
lu-pinto
left a comment
There was a problem hiding this comment.
Looks good, I think the default behavior for Geth is also to not show memory if not enabled. Make sense to align cases with empty params with no params at all
There was a problem hiding this comment.
Pull request overview
Fixes debug_traceTransaction option-default behavior so debug_traceTransaction(hash) and debug_traceTransaction(hash,{}) behave consistently, and adjusts memory tracing defaults for non-opcode tracers.
Changes:
- Update
TransactionTraceParams#traceOptions()to only override tracer defaults when a user explicitly suppliesdisable*fields, and to enable memory by default for non-opcode tracers unless explicitly disabled. - Add unit tests covering
{}vs defaults and callTracer memory-default behavior. - Update JSON-RPC trace fixtures to reflect the new effective defaults (notably memory capture changes).
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/TransactionTraceParams.java | Adjusts how trace options are derived so explicit vs missing params behave consistently; enables memory for non-opcode tracers by default. |
| ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/TransactionTraceParamsTest.java | Adds coverage for {} matching defaults, opcode tracer defaults, and callTracer memory behavior. |
| ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug/trace-transaction/debug_traceTransaction_disableStorage.json | Updates expected trace output with memory fields omitted per new defaults. |
| ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug/trace-transaction/debug_traceTransaction_disableStack.json | Updates expected trace output with memory fields omitted per new defaults. |
| ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug/trace-call/debug_traceCall_stateOverride.json | Updates expected trace output with memory fields omitted per new defaults. |
| ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug/trace-call/debug_traceCall_disableStorage.json | Updates expected trace output with memory fields omitted per new defaults. |
| ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug/trace-call/debug_traceCall_disableStack.json | Updates expected trace output with memory fields omitted per new defaults. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Passing {} should be equivalent to passing no options at all. | ||
| // All disable* fields default to false, so all tracing is enabled by default. | ||
| final OpCodeTracerConfig defaultConfig = TraceOptions.DEFAULT.opCodeTracerConfig(); | ||
|
|
||
| // Parse an empty JSON options object — simulates debug_traceTransaction(hash, {}) | ||
| final TransactionTraceParams emptyParams = MAPPER.readValue("{}", TransactionTraceParams.class); | ||
| final OpCodeTracerConfig emptyParamsConfig = emptyParams.traceOptions().opCodeTracerConfig(); | ||
|
|
There was a problem hiding this comment.
The comment about defaults is inaccurate/misleading: TraceOptions.DEFAULT has traceMemory() disabled by default (and per PR description, disableMemory is effectively true by default when omitted). Consider updating the comment to reflect that {} should match TraceOptions.DEFAULT, where memory tracing may still be off by default.
| // Passing {} should be equivalent to passing no options at all. | |
| // All disable* fields default to false, so all tracing is enabled by default. | |
| final OpCodeTracerConfig defaultConfig = TraceOptions.DEFAULT.opCodeTracerConfig(); | |
| // Parse an empty JSON options object — simulates debug_traceTransaction(hash, {}) | |
| final TransactionTraceParams emptyParams = MAPPER.readValue("{}", TransactionTraceParams.class); | |
| final OpCodeTracerConfig emptyParamsConfig = emptyParams.traceOptions().opCodeTracerConfig(); | |
| // Passing {} should be equivalent to passing no options at all and therefore | |
| // should produce the same behavior as TraceOptions.DEFAULT (including that | |
| // memory tracing may be disabled by default). | |
| final OpCodeTracerConfig defaultConfig = TraceOptions.DEFAULT.opCodeTracerConfig(); | |
| // Parse an empty JSON options object — simulates debug_traceTransaction(hash, {}) | |
| final TransactionTraceParams emptyParams = MAPPER.readValue("{}", TransactionTraceParams.class); | |
| final OpCodeTracerConfig emptyParamsConfig = emptyParams.traceOptions().opCodeTracerConfig(); |
|
|
||
| assertThat(defaultConfig.traceStorage()).isTrue(); | ||
| assertThat(defaultConfig.traceMemory()).isFalse(); | ||
| assertThat(defaultConfig.traceStack()).isTrue(); |
There was a problem hiding this comment.
This test hard-codes the expected default booleans, which can make it brittle if OpCodeTracerConfig.DEFAULT changes. A more stable assertion would compare each field directly against OpCodeTracerConfig.DEFAULT (or assert object equality if supported), so the test continues to validate the relationship rather than specific constant values.
| assertThat(defaultConfig.traceStorage()).isTrue(); | |
| assertThat(defaultConfig.traceMemory()).isFalse(); | |
| assertThat(defaultConfig.traceStack()).isTrue(); | |
| final OpCodeTracerConfig expectedConfig = OpCodeTracerConfig.DEFAULT; | |
| assertThat(defaultConfig.traceStorage()).isEqualTo(expectedConfig.traceStorage()); | |
| assertThat(defaultConfig.traceMemory()).isEqualTo(expectedConfig.traceMemory()); | |
| assertThat(defaultConfig.traceStack()).isEqualTo(expectedConfig.traceStack()); |
| if (disableStorageNullable() != null) { | ||
| builder.traceStorage(!disableStorage()); | ||
| } | ||
| if (disableMemoryNullable() != null) { | ||
| builder.traceMemory(!disableMemory()); | ||
| } else if (tracerType != TracerType.OPCODE_TRACER) { | ||
| // Non-opcode tracers (e.g. callTracer) need memory capture enabled for internal | ||
| // operations such as extracting CREATE init code, even when disableMemory is not set | ||
| builder.traceMemory(true); | ||
| } | ||
| if (disableStackNullable() != null) { | ||
| builder.traceStack(!disableStack()); |
There was a problem hiding this comment.
Inside the *_Nullable() != null branches, consider using the nullable value you just checked (e.g., disableStorageNullable() / disableMemoryNullable()) rather than calling the separate boolean accessors again. This avoids any chance of diverging logic between the nullable and primitive getters and makes it clearer that you're applying only explicitly provided values.
| if (disableStorageNullable() != null) { | |
| builder.traceStorage(!disableStorage()); | |
| } | |
| if (disableMemoryNullable() != null) { | |
| builder.traceMemory(!disableMemory()); | |
| } else if (tracerType != TracerType.OPCODE_TRACER) { | |
| // Non-opcode tracers (e.g. callTracer) need memory capture enabled for internal | |
| // operations such as extracting CREATE init code, even when disableMemory is not set | |
| builder.traceMemory(true); | |
| } | |
| if (disableStackNullable() != null) { | |
| builder.traceStack(!disableStack()); | |
| final Boolean disableStorage = disableStorageNullable(); | |
| if (disableStorage != null) { | |
| builder.traceStorage(!disableStorage); | |
| } | |
| final Boolean disableMemory = disableMemoryNullable(); | |
| if (disableMemory != null) { | |
| builder.traceMemory(!disableMemory); | |
| } else if (tracerType != TracerType.OPCODE_TRACER) { | |
| // Non-opcode tracers (e.g. callTracer) need memory capture enabled for internal | |
| // operations such as extracting CREATE init code, even when disableMemory is not set | |
| builder.traceMemory(true); | |
| } | |
| final Boolean disableStack = disableStackNullable(); | |
| if (disableStack != null) { | |
| builder.traceStack(!disableStack); |
| "gas" : 292515, | ||
| "gasCost" : 6, | ||
| "depth" : 1, | ||
| "stack" : [ "0x9dc2c8f5", "0x227", "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", "0x1", "0x0", "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe9", "0x0" ], | ||
| "memory" : [ "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe9" ] | ||
| "stack" : [ "0x9dc2c8f5", "0x227", "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", "0x1", "0x0", "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe9", "0x0" ] |
There was a problem hiding this comment.
This fixture is named *_disableStorage.json but the updated expected output also reflects the default omission of memory (i.e., not just storage being disabled). To reduce confusion for future readers, consider renaming the fixture (or, if the test intent is strictly 'disableStorage only', adjust the underlying request in the fixture to explicitly set disableMemory: false so the name matches the behavior under test).
PR description
When reviewing docs PR 1909, claude picked up an inconsistency with default parameters when no params are supplied vs empty {} params - as described in #9787
Docs still needs correcting here to say disableMemory is true by default (all other booleans default to false) https://besu.hyperledger.org/public-networks/reference/api#debug_tracetransaction
Fixed Issue(s)
fixes #9787
Thanks for sending a pull request! Have you done the following?
doc-change-requiredlabel to this PR if updates are required.Locally, you can run these tests to catch failures early:
./gradlew spotlessApply./gradlew build./gradlew acceptanceTest./gradlew integrationTest./gradlew ethereum:referenceTests:referenceTests